/*
   Copyright (c) 2011 Mizar Tools Contributors (mizartools.org)

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
/*  Contributors :
 *	2011-03-20 Marco Riccardi - initial implementation
 *
 */
package org.mizartools.utility;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.List;

public class ProcessManager {

	private boolean isRunning = false;
	private File workDirectory = null;
	private LinkedList<String> command = null;
	private String lastOutput = "";
	private boolean redirectOutput = false;
	private boolean captureOutput = false;
	private boolean captureLastOutput = false;
	private Thread thread = null;
	private Job job = null;
	private int exitValue = -1; 
	private Exception exception = null;
	private LinkedList<String> output = null;
	private int priority = Thread.NORM_PRIORITY;
	private IOutputAnalyzer outputAnalyzer = null;
	
	private class Job implements Runnable {

		public File workDirectory;
		public List<String> command;
		public boolean redirectOutput = false;
		public boolean captureOutput = false;
		public boolean captureLastOutput = false;
		public ProcessManager processManager;
		private boolean toTerminate = false;
		private LinkedList<String> output = null;
		
		@Override
		public void run() {
			ProcessBuilder pb = new ProcessBuilder();
			pb.redirectErrorStream(true);
			pb.directory(workDirectory);
			pb.command(command);
			if (captureOutput) output = new LinkedList<String>(); 
			try {
				Process p = pb.start();
				InputStream inputStream = p.getInputStream();
				InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
				BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
				String line = null;
				while ((line = bufferedReader.readLine()) != null) {
					if (isToTerminate()) p.destroy();
					if (redirectOutput)	System.out.println(line);
					if (captureLastOutput) processManager.setLastOutput(line);
					if (captureOutput) output.add(line);
					if (outputAnalyzer != null) outputAnalyzer.analyze(line);
				}
				p.waitFor();
				processManager.setExitValue(p.exitValue());
				processManager.setOutput(output);
			} catch (IOException e) {
				processManager.setException(e);
			} catch (InterruptedException e) {
				processManager.setException(e);
			}
			processManager.setRunning(false);
		}

		private boolean isToTerminate() {
			synchronized (Job.this) {
				return toTerminate;
			}
		}

		public void setToTerminate(boolean toTerminate) {
			synchronized (Job.this) {
				this.toTerminate = toTerminate;
			}
		}
	}
	
	public void start(){
		this.job = new Job();
		this.job.processManager = this;
		this.job.workDirectory = workDirectory;
		this.job.command = command;
		this.job.redirectOutput = redirectOutput;
		this.job.captureLastOutput = captureLastOutput;
		this.job.captureOutput = captureOutput;
		this.thread = new Thread(this.job);
		this.thread.setPriority(priority);
		this.setExitValue(-1);
		this.setRunning(true);
		this.thread.start();
	}
	
	public void waitProcess() {
		if (this.thread == null) return;
		while (this.thread.isAlive()) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	public void waitProcess(long timeout) {
		if (this.thread == null) return;
		while (this.thread.isAlive() && timeout > 0) {
			try {
				Thread.sleep(100);
				timeout = timeout - 100;
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public void stop(){
		if (this.thread == null) return;
		if (this.thread.isAlive()) {
			this.job.setToTerminate(true);
		}
	}

	public ProcessManager(File workDirectory, LinkedList<String> command){
		this.workDirectory = workDirectory;
		LinkedList<String> myCommand = new LinkedList<String>();
		for(String s : command) {
			myCommand.add(s);
		}
		this.command = myCommand;
	}
	
	public File getWorkDirectory() {
		return workDirectory;
	}

	public boolean isRedirectOutput() {
		return redirectOutput;
	}

	public LinkedList<String> getCommand() {
		return command;
	}

	private void setRunning(boolean isRunning) {
		synchronized (ProcessManager.this) {
			this.isRunning = isRunning;
		}
	}

	public boolean isRunning() {
		synchronized (ProcessManager.this) {
			return isRunning;
		}
	}

	private void setExitValue(int exitValue) {
		synchronized (ProcessManager.this) {
			this.exitValue = exitValue;
		}
	}

	public int getExitValue() {
		synchronized (ProcessManager.this) {
			return exitValue;
		}
	}

	private void setException(Exception exception) {
		synchronized (ProcessManager.this) {
			this.exception = exception; 
		}
	}

	public Exception getException() {
		synchronized (ProcessManager.this) {
			return exception;
		}
	}

	public String getLastOutput() {
		synchronized (ProcessManager.this) {
			return lastOutput;
		}
	}

	private void setLastOutput(String lastOutput) {
		synchronized (ProcessManager.this) {
			this.lastOutput = lastOutput;
		}
	}

	public void setCaptureOutput(boolean captureOutput) {
		this.captureOutput = captureOutput;
	}

	public void setOutput(LinkedList<String> output) {
		this.output = output;
	}
	
	public LinkedList<String> getOutput() {
		return output;
	}

	public void setCaptureLastOutput(boolean captureLastOutput) {
		this.captureLastOutput = captureLastOutput;
	}

	public void setPriority(int priority) {
		this.priority = priority;
	}

	public void setRedirectOutput(boolean redirectOutput) {
		this.redirectOutput = redirectOutput;
	}

	public void setProcessOutputAnalyzer(IOutputAnalyzer outputAnalyzer) {
		this.outputAnalyzer = outputAnalyzer;
	}

}
